package luahelper

import (
	"fmt"
	lua "github.com/yuin/gopher-lua"
	"reflect"
)

func PushLuaData(l *lua.LState, items ...lua.LValue) int {
	for _, item := range items {
		l.Push(item)
	}

	return len(items)
}

func PushLuaStatusWithData(l *lua.LState, status bool, items ...lua.LValue) int {
	l.Push(lua.LBool(status))

	for _, item := range items {
		l.Push(item)
	}

	return len(items) + 1
}

func PushLuaStatus(l *lua.LState, status bool) int {
	l.Push(lua.LBool(status))

	return 1
}

func PushLuaStatusWithMessage(l *lua.LState, status bool, message string) int {
	l.Push(lua.LBool(status))
	l.Push(lua.LString(message))

	return 2
}

func GoInstanceIndex_4lua(l *lua.LState) int {
	defer func() {
		if err, ok := recover().(error); ok {
			fmt.Println("GoInstance Index call error", err)
		}
	}()

	self := l.ToTable(1)
	if self == nil {
		return 0
	}

	fieldName := l.ToString(2)

	goinstance := GetGoInstanceFormLuaTable(l, self)
	if goinstance == nil || goinstance == lua.LNil {
		return 0
	}

	selectionType := reflect.ValueOf(goinstance)
	gotype := reflect.TypeOf(goinstance)

	if gotype.Kind() == reflect.Ptr {
		if _, ok := gotype.Elem().FieldByName(fieldName); ok {
			l.Push(ConvertValue2LValue(l, selectionType.Elem().FieldByName(fieldName)))
			return 1
		}
	} else {
		if _, ok := gotype.FieldByName(fieldName); ok {
			l.Push(ConvertValue2LValue(l, selectionType.FieldByName(fieldName)))
			return 1
		}
	}

	if _, ok := gotype.MethodByName(fieldName); ok {
		method := selectionType.MethodByName(fieldName)
		if method.Kind() == reflect.Func {
			l.Push(l.NewFunction(GetGoInstanceIndexFunc(fieldName)))
			return 1
		}
	}

	return 0
}

func ConvertValue2LValue(l *lua.LState, value reflect.Value) lua.LValue {
	kind := value.Kind()
	if kind == reflect.String {
		return lua.LString(value.String())
	} else if kind == reflect.Int || kind == reflect.Int8 || kind == reflect.Int16 || kind == reflect.Int32 || kind == reflect.Int64 {
		return lua.LNumber(value.Int())
	} else if kind == reflect.Float64 || kind == reflect.Float32 {
		return lua.LNumber(value.Float())
	} else if kind == reflect.Bool {
		return lua.LBool(value.Bool())
	} else {
		return &lua.LUserData{
			Value:     value,
			Metatable: ConvertGoInstance2LuaTable(l, value),
		}
	}
}

func ConvertLValue2Value(value lua.LValue, kind reflect.Kind) reflect.Value {
	valuetype := value.Type()
	if valuetype == lua.LTNumber {
		if kind == reflect.Int {
			return reflect.ValueOf(int(lua.LVAsNumber(value)))
		} else if kind == reflect.Int32 {
			return reflect.ValueOf(int32(lua.LVAsNumber(value)))
		} else if kind == reflect.Int16 {
			return reflect.ValueOf(int32(lua.LVAsNumber(value)))
		}
	} else if valuetype == lua.LTString {
		return reflect.ValueOf(string(lua.LVAsString(value)))
	}

	return reflect.Value{}
}

func GetGoInstanceIndexFunc(funcName string) func(*lua.LState) int {
	return func(l *lua.LState) int {
		self := l.ToTable(1)
		if self == nil {
			fmt.Println("not found self, lua call shoud use ':' not use '.'")
			return 0
		}

		goinstance := GetGoInstanceFormLuaTable(l, self)
		if goinstance == nil {
			return 0
		}

		selftype := reflect.TypeOf(goinstance)
		method, ok := selftype.MethodByName(funcName)
		if ok == false {
			return 0
		}

		var values []reflect.Value
		//values = append(values, reflect.ValueOf(goinstance))
		for i := 1; i < method.Type.NumIn(); i++ {
			paramter := l.Get(i + 1)
			v := ConvertLValue2Value(paramter, method.Type.In(i).Kind())
			values = append(values, v)
		}

		respvalues := reflect.ValueOf(goinstance).MethodByName(funcName).Call(values)

		for _, item := range respvalues {
			l.Push(ConvertValue2LValue(l, item))
		}

		return len(respvalues)
	}
}

func ConvertGoInstance2LuaTable(l *lua.LState, item interface{}) (table *lua.LTable) {
	metatable := l.NewTable()

	gostruct := l.NewUserData()
	gostruct.Value = item
	metatable.RawSetString("__gostruct", gostruct)
	metatable.RawSetString("__index", l.NewFunction(GoInstanceIndex_4lua))

	table = l.NewTable()
	l.SetMetatable(table, metatable)

	return
}

func GetGoInstanceFormLuaTable(l *lua.LState, table *lua.LTable) (goinstance interface{}) {
	metaTable := l.GetMetatable(table)
	if metaTable != nil {
		gostruct := l.GetField(metaTable, "__gostruct")

		if gostruct != nil {
			if userdata, ok := gostruct.(*lua.LUserData); ok {
				goinstance = userdata.Value
			}
		}
	}

	return
}
